#include "minicon.h"
#include "miniopt.h"

extern int err_ret(int errcode, const char *msg, const WCHAR *name);

//===========================================================================================================
//===========================================================================================================

static void count_line( LARGE_INTEGER& nlin )
{
   ++nlin.QuadPart;
   if (nlin.HighPart) { Con::o(nlin.QuadPart); Con::o("  "); }
   else Con::f("%6u  ", nlin.LowPart);
}

//-----------------------------------------------------------------------------------------------------------

static void new_line_hell(
   WCHAR curr_ch, WCHAR prev_ch, WCHAR pp_ch,
   LARGE_INTEGER& nlin, bool prev_NL,
   int num, bool squeeze, bool ends
) {
   if ( !prev_NL ) { // end of non-empty line
      if ( ends ) Con::o('$');
      Con::o(&curr_ch, 1);
      return;
   }

   if ( curr_ch == L'\r' || prev_ch == L'\n' ) { // NL+CR , CR+CR or NL+NL -- end of empty line
      if ( squeeze )
         return;
      if ( num > 0 )
         count_line(nlin);
      if ( ends )
         Con::o('$');
      Con::o(&curr_ch, 1);
      return;
   }

   // CR+LF -- continued end of line
   if ( pp_ch != L'\r' && pp_ch != L'\n' ) { // line was not empty
      Con::o('\n');
      return;
   }

   // continued end of empty line
   if ( !squeeze )
      Con::o('\n');
}

//-----------------------------------------------------------------------------------------------------------

static int cat( const WCHAR* srcn, int num, bool nonpr, bool tab, bool ends, bool squeeze )
{
   int i, rc = 0, mch, no;
   if (!Con::setInp(srcn)) {
      rc = err_ret(-23, "Can't open input", srcn);
      return rc;
   }

   static const int buff_size = 64 * 1024;
   static WCHAR *pbf = NULL; 
   Win::alloc((void* &)pbf, buff_size);

   HANDLE fhi = INVALID_HANDLE_VALUE, fho = INVALID_HANDLE_VALUE;
   bool not_conv = (num == 0) && !nonpr && !tab && !ends && !squeeze;
   bool raw_mode = not_conv && Con::getRawFH(fhi, fho);

   if (raw_mode) {
      mch = buff_size;
      for (;;) {
         DWORD dwr = 0, dww = 0;
         BOOL res = ::ReadFile(fhi, pbf, (DWORD)mch, &dwr, NULL);
         if (!res && ::GetLastError() != ERROR_BROKEN_PIPE)
         { rc = err_ret(-26, "read error:", srcn); }
         if ( rc < 0 || !dwr ) break;

         res = ::WriteFile(fho, pbf, dwr, &dww, NULL);
         if (!res || dwr != dww)
         { rc = err_ret(-25, "write error:", L"-"); }
      }
   }

   else {
      mch = (int)(buff_size / sizeof(WCHAR));
      LARGE_INTEGER nlin; nlin.QuadPart = 0;
      WCHAR curr_ch=L'\n', ctrl[2] = L"^", prev_ch=L'\r', pp_ch, *po;
      bool prev_NL = true;

      for (;;) {
         if ( (rc = Con::read(pbf, mch)) < 0 )
         { err_ret(rc, "read error:", srcn); break; }
         if (0 == rc)
            break;

         if ( not_conv ) {
            if ( (rc = Con::write(pbf, rc)) < 0 )
            { err_ret(rc, "write error:", L"-"); break; }
            continue;
         }

         i = 0;
         while (i < rc) {
            pp_ch = prev_ch; prev_ch = curr_ch; curr_ch = pbf[i++];
            po = &curr_ch; no = 1;
            if ( curr_ch < 0x20 ) {
               if ( curr_ch == L'\t' ) {
                  if ( tab )
                  { po = ctrl; ctrl[1] = L'I'; no = 2; }
               }
               else if ( curr_ch == L'\n' || curr_ch == L'\r' ) {
                  new_line_hell(curr_ch,prev_ch,pp_ch, nlin,prev_NL, num,squeeze,ends);
                  prev_NL = true;
                  continue;
               }
               else {
                  if ( nonpr )
                  { po = ctrl; ctrl[1] = (WCHAR)(L'@' + curr_ch); no = 2; }
               }
            }
            else {
               if ( curr_ch == 0x7f && nonpr )
               { po = ctrl; ctrl[1] = L'?'; no = 2; }
            }

            if ( num && prev_NL )
               count_line(nlin);

            prev_NL = false;
            Con::o(po, no);
         }
      }
   }

   Con::flush(0x3); // flush inp and out
   return rc;
}

//-----------------------------------------------------------------------------------------------------------

int cat( WCHAR **args,int na, int num, bool nonpr, bool tab, bool ends, bool squeeze )
{
   int i = 0, rc = 0;
   WCHAR *pw, srcn[MAX_PATH*2];
 
   if ( na < 1 ) { args[0] = L"-"; na = 1; }

   while ( i < na && 0 == rc )
   {
      if (Opt::have_wildcard(pw = args[i++]))
      {
         WIN32_FIND_DATAW ff;
         HANDLE hFind = ::FindFirstFileW(pw, &ff);
         if ( INVALID_HANDLE_VALUE == hFind )
            continue;

         ::lstrcpynW(srcn, pw, MAX_PATH); srcn[MAX_PATH] = L'\0';
         pw = srcn + ::lstrlenW(srcn);
         while (pw > srcn && !Opt::is_slash(pw[-1]) && pw[-1] != L':') --pw;
         do {
            if (Opt::is_dot(ff.cFileName))
               continue;
            ::lstrcpynW(pw, ff.cFileName, MAX_PATH); pw[MAX_PATH-1] = L'\0';
            rc = cat(srcn, num, nonpr, tab, ends, squeeze);
         } while ( 0 == rc && ::FindNextFileW(hFind, &ff) );

         ::FindClose(hFind);
      }
      else
         rc = cat(pw, num, nonpr, tab, ends, squeeze);
   }
   return rc;
}

//===========================================================================================================
//===========================================================================================================
